package com.doodle.common.utils;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.doodle.common.data.HttpResultBean;
import com.doodle.common.data.ResultModel;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.List;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;

public class HttpUtils {
	
	public static final String GET = "GET";
	public static final String POST = "POST";
	
    private static String TAG = "HttpUtils";
    
	public static final int HTTP_CONNECT_TIMEOUT = 30000;
    public static final int HTTP_READ_TIMEOUT = 30000;
    public static final int BUFFER_LENGTH = 4096;
    
    public static boolean SHOW_LOG = false;
    public static boolean SAVE_LOG_TO_FILE = false;
    
    private static final String HTTP_POST_BOUNDARY = "--------MPCS_HTTP_POST_CONTENT_BOUNDARY";
    private static final byte[] HTTP_POST_PARAM_END_BYTES = "\r\n".getBytes();
    private static final byte[] HTTP_POST_ALL_PARAMS_END_BYTES = ("--" + HTTP_POST_BOUNDARY + "--\r\n").getBytes();
    
    public static interface HeaderConsumer {
    	void consume(String name, String vlaue);
    }
    
    public static interface HeaderEnumerater {
    	void enumerate(HeaderConsumer consumer);
    }
    
    public static class FileEntry {
    	public String mPath;
    	public long mStart;
    	public long mLength;
    	public long mDoneLength;
    }
    
    public static class UploadFileEntry extends FileEntry {
    	public String mKey;
    	public String mMime;
    	private byte[] mFileBeginBytes;
    }
    
    private static <T> boolean checkCookie(ResultModel<T> result, CookieHandler cookie) {
		if (result != null && cookie != null) {
    		if (!cookie.isCookieValid(result.getReturnCode())) {
    			if (cookie.updateCookie()) {
    				return true;
    			}
    		}
		}
		return false;
	}
    
    private static class CookieHeaderEnumerater implements HeaderEnumerater {
		
		private CookieHandler mCookie;
		
		public CookieHeaderEnumerater(CookieHandler cookie) {
			mCookie = cookie;
		}
		
		public void enumerate(HeaderConsumer consumer) {
			consumer.consume("Cookie", mCookie.getCookie());
		}
	}
	
	public static <T> ResultModel<T> doHttpGetAndParse(String urlString, TypeReference<ResultModel<T>> typeRef, CookieHandler cookie) {
		CookieHeaderEnumerater enumerater = cookie == null ? null : new CookieHeaderEnumerater(cookie);
		String jsonStr = doHttpRequest(GET, urlString, null, enumerater);
		ResultModel<T> result = JSONUtils.parseResultModel(jsonStr, typeRef);
		if (checkCookie(result, cookie)) {
			jsonStr = doHttpRequest(GET, urlString, null, enumerater, null);
			return JSONUtils.parseResultModel(jsonStr, typeRef);
		} else {
			return result;
		}
	}
	
	private static class HttpRequestHeaderConsumer implements HeaderConsumer {
		
		private HttpURLConnection mConnection;
		
		public HttpRequestHeaderConsumer(HttpURLConnection connection) {
			mConnection = connection;
		}
		
		public void consume(String name, String value) {
			mConnection.setRequestProperty(name, value);
		}
	}
	
	private static final HostnameVerifier sHostnameVerifier = new HostnameVerifier() {
		@Override
		public boolean verify(String hostname, SSLSession session) {
			return true;
		}
	};
	
	public static String doHttpRequest(String method, String urlString, 
			List<PostParameter> postParams, HeaderEnumerater enumerater) {
		return doHttpRequest(method, urlString, postParams, enumerater, null, null);
	}
	
	public static String doHttpRequest(String method, String urlString, 
			List<PostParameter> postParams, HeaderEnumerater enumerater, TokenHandler token) {
		return doHttpRequest(method, urlString, postParams, enumerater, token, null);
	}

	public static String doHttpRequest(String method, String urlString, List<PostParameter> postParams, HeaderEnumerater enumerater, TokenHandler token, boolean needHttpCode) {
		return doHttpRequest(method, urlString, postParams, enumerater, token, null, needHttpCode);
	}

	public static String doHttpRequest(String method, String urlString,
			List<PostParameter> postParams, HeaderEnumerater enumerater, TokenHandler token, List<FileEntry> fileEntries, Object... args) {
		Utils.log(SHOW_LOG, SAVE_LOG_TO_FILE, "XXX: %s", urlString);
		HttpURLConnection connection = null;
		InputStreamReader streamReader = null;
		BufferedReader bufferedReader = null;
		boolean ifNeedHttpCode = false;
		int responseCode = -1;
		String responseErrorContent = null;
		String responseContent = null;
		try {

			if (args != null && args.length > 0) {
				Object obj = args[0];
				if (obj != null && obj instanceof Boolean) {
					ifNeedHttpCode = (Boolean)obj;
				}
			}			

			connection = (HttpURLConnection) new URL(urlString).openConnection();
			connection.setConnectTimeout(HTTP_CONNECT_TIMEOUT);
			connection.setReadTimeout(HTTP_READ_TIMEOUT);
			connection.setDoInput(true);

			if (enumerater != null) {
				enumerater.enumerate(new HttpRequestHeaderConsumer(connection));
			}
			
			if (connection instanceof HttpsURLConnection) {
				((HttpsURLConnection)connection).setHostnameVerifier(sHostnameVerifier);
			}
			
			if (POST.equals(method)) {
				connection.setRequestMethod(POST);
				connection.setDoOutput(true);
				sendPostContent(connection, postParams, fileEntries);
			} else {
				connection.setRequestMethod(GET);
				connection.setDoOutput(false);
				connection.setRequestProperty("x-requested-with", "XMLHttpRequest");
			}
			responseCode = connection.getResponseCode();
			
			if (token != null) {
				connection.connect();
				if (!token.isTokenValid(connection.getResponseCode())) {
					if (token.updateToken()) {
						connection.disconnect();
						connection = null;
						return doHttpRequest(method, urlString, postParams, enumerater, null, fileEntries, ifNeedHttpCode);
					}
				}
			}
			
			InputStream is = connection.getErrorStream();
			if (is == null) {
				is = connection.getInputStream();
			}
			streamReader = new InputStreamReader(is);
        	bufferedReader = new BufferedReader(streamReader);
        	StringBuilder buffBuilder = new StringBuilder();
            String line;
            while (null != (line = bufferedReader.readLine())) {
            	Utils.log(SHOW_LOG, SAVE_LOG_TO_FILE, "XXX: %s", line);
            	buffBuilder.append(line).append("\n");
            }
            responseContent = buffBuilder.toString();
		} catch (Exception e) {
			if (SHOW_LOG || SAVE_LOG_TO_FILE) {
				Utils.log(SHOW_LOG, SAVE_LOG_TO_FILE, "Exception : %s", "urlString = " + urlString + ", e.toString() = " + e);
			} else {
				Utils.log("Exception : %s", "e.toString() = " + e);
			}
			if (ifNeedHttpCode) {//返回结果需要返回码
				responseErrorContent = e.toString();
			}
		} finally {
			if (null != connection) {
				connection.disconnect();
			}
			Utils.closeSilently(bufferedReader);
			Utils.closeSilently(streamReader);
		}
		if (ifNeedHttpCode) {//返回结果需要返回码
			HttpResultBean resultBean = new HttpResultBean();
			resultBean.setResponseCode(responseCode);
			resultBean.setResponseContent(responseContent);
			resultBean.setResponseErrorContent(responseErrorContent);
			return JSONObject.toJSONString(resultBean);
		} else {//正常返回
			return responseContent;
		}		
	}
	
	private static void sendPostContent(HttpURLConnection connection, List<PostParameter> postParams,
			List<FileEntry> fileEntries) throws IOException, UnsupportedEncodingException {
		int paramCount = postParams == null ? 0 : postParams.size();
		int fileCount = fileEntries == null ? 0 : fileEntries.size();
		if (paramCount == 0 && fileCount == 0) {
			return;
		}
		
		connection.setUseCaches(false);
		
		OutputStream os = null;
		try {
			if (fileCount == 0) {
				if (connection.getRequestProperty("Content-Type") == null) { //未设置该字段
					connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
				}
				
				StringBuilder builder = new StringBuilder();
				for (PostParameter param : postParams) {
					if (builder.length() > 0) {
						builder.append('&');
					}
					if (param.getName() == null || param.getName().length() == 0) {
						builder.append(param.getParamValue());
					} else {
						builder.append(param.getName()).append('=').append(param.getParamValue());
					}
				}
				Utils.log(SHOW_LOG, SAVE_LOG_TO_FILE,"XXX: %s", builder.toString());
				byte[] bytes = builder.toString().getBytes("UTF-8");
				connection.setRequestProperty("Content-Length", String.valueOf(bytes.length));
				
				os = connection.getOutputStream();
				os.write(bytes);
			} else {
				connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + HTTP_POST_BOUNDARY);

				StringBuilder builder = new StringBuilder();
				
				int totalBytes = 0;
				byte[] stringBytes = null;
				if (paramCount > 0) {
					for (PostParameter param : postParams) {
						appendStringEntry(builder, param);
					}
					Utils.log(SHOW_LOG, SAVE_LOG_TO_FILE, "XXX: %s", builder.toString());
					stringBytes = builder.toString().getBytes("UTF-8");
					totalBytes += stringBytes.length;
				}
				
				for (FileEntry entry : fileEntries) {
					UploadFileEntry ufe = (UploadFileEntry)entry;
					builder.setLength(0);
					appendFileEntryBegin(builder, ufe);
					Utils.log(SHOW_LOG, SAVE_LOG_TO_FILE, "XXX: %s", builder.toString());
					ufe.mFileBeginBytes = builder.toString().getBytes("UTF-8");
					totalBytes += ufe.mFileBeginBytes.length;
					if (ufe.mStart == 0 && ufe.mLength == 0) {
						totalBytes += new File(ufe.mPath).length();
					} else {
						totalBytes += ufe.mLength;
					}
					totalBytes += HTTP_POST_PARAM_END_BYTES.length;
				}
				
				totalBytes += HTTP_POST_ALL_PARAMS_END_BYTES.length;
				
				connection.setFixedLengthStreamingMode(totalBytes);
				
				os = connection.getOutputStream();
				
				if (stringBytes != null) {
					os.write(stringBytes);
				}
				for (FileEntry entry : fileEntries) {
					UploadFileEntry ufe = (UploadFileEntry)entry;
					os.write(ufe.mFileBeginBytes);
					sendFileEntry(ufe, os);
					os.write(HTTP_POST_PARAM_END_BYTES);
				}
				os.write(HTTP_POST_ALL_PARAMS_END_BYTES);
			}
			os.flush();
		} finally {
			Utils.closeSilently(os);
		}
	}
	
	private static void appendStringEntry(StringBuilder builder, PostParameter param) {
		builder.append("--").append(HTTP_POST_BOUNDARY).append("\r\n");
		builder.append("Content-Disposition: form-data; name=\"").append(param.getName()).append("\"\r\n");
		builder.append("\r\n");
		builder.append(param.getParamValue()).append("\r\n");
	}
	
	private static void appendFileEntryBegin(StringBuilder builder, UploadFileEntry entry) throws UnsupportedEncodingException {
		builder.append("--").append(HTTP_POST_BOUNDARY).append("\r\n");
		builder.append("Content-Disposition: form-data; name=\"").append(entry.mKey).append("\"; ");
		builder.append("filename=\"").append(URLEncoder.encode(getFileName(entry.mPath), "UTF-8")).append("\"\r\n");
		builder.append("Content-Type: ").append(entry.mMime).append("\r\n\r\n");
	}
	
	private static boolean sendFileEntry(UploadFileEntry entry, OutputStream os) throws FileNotFoundException, IOException {
		if (entry.mStart == 0 && entry.mLength == 0) {
			FileInputStream fis = null;
			try {
				fis = new FileInputStream(entry.mPath);
				if (!dump(fis, os, fis.available())) {
					return false;
				}
			} finally {
				Utils.closeSilently(fis);
			}
		} else {
			RandomAccessFile raf = null;
			try {
				raf = new RandomAccessFile(entry.mPath, "r");
				if (!dump(raf, os, entry.mStart, entry.mLength)) {
					return false;
				}
			} finally {
				Utils.closeSilently(raf);
			}
		}
		return true;
	}
	
	public static boolean dump(InputStream is, OutputStream os, int total) throws IOException {
        byte buffer[] = new byte[BUFFER_LENGTH];
        int rc = is.read(buffer, 0, buffer.length);
        if (rc <= 0) {
        	return false;
        }
        int current = 0;
        while (rc > 0) {
            /*if (jc.isCancelled()) {
            	throw new InterruptedIOException();
            }*/
            os.write(buffer, 0, rc);
            current += rc;
            Utils.log("dump: %d ", current);
            rc = is.read(buffer, 0, buffer.length);
        }
        return true;
    }
	
	public static boolean dump(RandomAccessFile raf, OutputStream os, long start, long length) throws IOException {
		raf.seek(start);
		
        byte buffer[] = new byte[BUFFER_LENGTH];
        
        int l = (int)Math.min(length, buffer.length);
        int rc = raf.read(buffer, 0, l);
        if (rc <= 0) {
        	return false;
        }
        
        while (rc > 0) {
            /*if (jc.isCancelled()) {
            	throw new InterruptedIOException();
            }*/
            os.write(buffer, 0, rc);
            
            length -= rc;
            if (length <= 0) {
            	return true;
            }
            
            l = (int)Math.min(length, buffer.length);
            rc = raf.read(buffer, 0, l);
        }
        
        return false;
    }
	
	public static String getFileName(String filePath) {
        int separatorIndex = filePath.lastIndexOf('/');
        return (separatorIndex < 0) ? filePath : filePath.substring(separatorIndex + 1, filePath.length());
    }

	private final static String[] sHexDigits = { "0", "1", "2", "3", "4", "5",
			"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

	public static String MD5Encode(String origin) {
		String resultString = null;
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			resultString = byteArrayToHexString(md.digest(origin.getBytes()));
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return resultString;
	}

	public static String byteArrayToHexString(byte[] b) {
		StringBuffer resultSb = new StringBuffer();
		for (int i = 0; i < b.length; i++) {
			resultSb.append(byteToHexString(b[i], true));
		}
		return resultSb.toString();
	}

	private static String byteToHexString(byte b, boolean bigEnding) {
		int n = b;
		if (n < 0) {
			n = 256 + n;
		}
		int d1 = n / 16;
		int d2 = n % 16;
		return (bigEnding) ? (sHexDigits[d1] + sHexDigits[d2])
				: (sHexDigits[d2] + sHexDigits[d1]);
	}
}
